package com.example.springboot;

import com.example.springboot.config.asm.CountTime;
import com.example.springboot.config.imports.ImportBeanDefinitionRegistrarImpl;
import com.example.springboot.config.imports.ImportSelectorImpl;
import com.example.springboot.config.logFilter.EnableLog;
import com.example.springboot.utils.Calculate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * 一、快速体验缓存
 * 步骤
 * 1 开启基于注解的缓存 @EnableCaching
 * 2 标注缓存注解即可
 * 二、整合redis来操作数据
 * 测试redis缓存：
 * 1 引入redis的starter后，容器中保存的redisCacheManager，simpleCacheManager将不生效
 * 2 springboot此时将会是redisCacheManager生效->创建redisCache来缓存
 * 3 保存数据为k-v的方式，利用序列化来保存。保存为json的方法是
 * <p>
 * 三、springboot与消息队列
 * 1 自动配置了连接工厂 connectionFactory
 * 2 封装了rabbitmq的配置
 * 3 提供了rabbitTemplate操作
 * 4 EnableRabbit+rabbitListener监听消息队列的内容
 * 5 AmqpAdmin Rabbitmq系统管理组件功能组件
 * AmqpAdmin 创建和删除Queue、Exchange、Binding
 * <p>
 * 四、elasticsearch
 * springboot默认支持两种技术来支持域es交互
 * 4.1 Jest 默认是不生效的 需要导入jar包 io.seachbox.client.jestClient
 * 4.2 springdata elasticsearch
 * Client的节点信息clusterNodes,clusterName
 * elasticsearchTemplate操作es
 * 编写一个ElasticsearchRepository的子接口来操作es
 * <p>
 * 五、引入SpringSecurity
 * 1 引入启动依赖
 * 2 编写配置类
 * 六、自定义健康状态指示器
 * 1 编写一个指示器，实现HealthIndicator接口
 * 2 指示器的名字 xxxHealIndicator
 * 3 加入容器
 * ImportResource 导入spring的配置文件 并使其生效
 * spring循环依赖解决：
 * 1 每次获取bean对象时先从一级缓存中取值
 * 2 如果没有则以beanName为key,BeanFactory中为value放入三级缓存中（此时bean已经实例化但是没有进行初始化）
 * 3 正常情况下，先创建A再创建B，但是如果有循环依赖会在创建A的过程中把B创建出来
 * 4 创建B时进行初始化时会拿到A实例化但是未初始化的对象，此时B会初始化完成，会把B放入一级缓存中
 * 5 此时会继续初始化A,将B填充到A的属性中，此时A初始化完成，将其放入一级缓存
 * 问题： ①一级缓存能否解决循环依赖：
 * 不能，只有一级缓存，无法判断是完全状态或非完全状态，或造成紊乱
 * ② 二级缓存能否解决问题，为什么需要三级缓存：
 * 三级缓存放入的匿名内部类（对象工厂），在三级缓存中防止匿名内部类，在于你所需要的类可能是简单对象，也有可能是需要进行代理对象
 * 当我向三级缓存中放入匿名内部类时，可以决定是放代理类还是普通类
 */
@EnableScheduling // 开启基于注解的定时任务
@EnableAsync // 开启异步任务功能
@EnableRabbit // 开启基于注解的rabbitmq
@EnableCaching
@ImportResource(locations = {"classpath:bean.xml"})
// 排除数据库自动配置类
/**
 * 自动装配流程：
 * springBootApplication->注解里面包含EnableAutoConfiguration注解，里面运用Import注解导入了AutoConfigurationImportSelector
 * 在对应的selectImport方法中会读取每个jar包下的spring.factory文件的需要自动装配的类，然后通过spring-atoconfigure-metadata.properties文件
 * 做条件过滤，返回需要自动装配的对象
 * starter的理解：
 *  starter作用是在meta-inf目录下提供一个spring.factories文件，在该文件中我们添加了一个需要注入到spring容器中的配置类
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
// 自动装配mapper->相当于给每个接口添加了@mapper注解
@MapperScan(value = "com.example.springboot.mapper")
// 开启自定义日志收集
@EnableLog
// 导入自己的ImportSelector实现类
@Import({ImportSelectorImpl.class, ImportBeanDefinitionRegistrarImpl.class})
@EnableHystrix
@EnableCircuitBreaker
/**
 * springBoot启动流程
 * 1 创建SpringApplication
 *   保存一些信息
 *   判定当前应用的类型
 *   找bootstrappers ：初始启动引导器（List<BootStrapper></> 去spring.factories中寻找）
 *   org.springframework.boot.bootstrapper
 *   找 applicationContextInitializer 去spring.factories中寻找ApplicationContextInitializer
 *   List<ApplicationContextInitializer></>
 *   找applicationListener 去spring.factories中寻找ApplicationListener
 * 2 运行run方法
 *  1 StopWatch 记录应用启动时间
 *  2 创建引导上下文（Context环境） createBootStrapContext（）
 *   h获取之前所有的bootStraper挨个执行initilizer()方法
 *   3 让当前应用进入head_less模式
 *   4 获取所有的RunListener()运行监听器 去spring.factories获取SpringApplicationRunListener
 *   5 遍历执行started方法(为了方便所有listener方便时间感知)
 *   6 保存命令行参数 ApplicationArguments
 *   7 准备环境
 *     返回或者创建基础唤醒信息对象 StandardServletEnvironment
 *     配置环境信息
 *      读取所有配置源的配置属性值
 *     绑定环境信息
 *     监听器调用，通知所有监听器，通知所有监听器环境准备完成
 *   8 创建ioc容器
 *     1 根据项目类型（Servlet） 创建容器
 *     2 当前会创建AnnotationConfigServletWebServerApplicationContext
 *   9 准备 applicationContext
 *     保存环境信息
 *     ioc容器的后置处理流程
 *     应用初始化器 applyInitializer
 *       遍历所有的ApplicationInitializer,调用initialize方法，来对ioc容器进行扩展
 *       遍历所有的listener调用contextPrepared EventPublishListener通知所有的监听器contextPrepared
 *       调用减轻器的contextLoaded方法
 *    10 刷新ioc容器 refreshContext
 *    11 调用ring器的listener的started方法
 *    12 调用所有的runner callRunner
 *      获取容器中ApplicationRunner
 *      获取容器椎间盘美好的CommandLineRunner
 *      合并所有的runner按order排序
 *
 *  二、springboot创建代理对象流程：
 *      类：AnnotationAwareAspectJAutoProxyCreator->postProcessAfterInitialization(Object,String)的方法
 *      然后执行AbstractAutoProxyCreator#wrapIfNecessary方法
 *      最后执行createProxy()方法放入spring容器中
 *
 *
 *
 */
public class SpringbootApplication implements CommandLineRunner {

    public static void main(String[] args) {
        // 加入刷新容器前的监听器
        SpringApplication.run(SpringbootApplication.class, args);
    }

    public static void countTime(Class<?> clazz) {
        // 拿到文件名
        String simpleName = clazz.getSimpleName();
        String path = clazz.getResource("").getPath();
        path = path + simpleName + ".class";
        CountTime.countTime(path);
    }

    @Override
    public void run(String... args) throws Exception {
        countTime(Calculate.class);
    }
}
